name: libclang ABI Tests

permissions:
  contents: read

on:
  workflow_dispatch:
  push:
    ignore-forks: true
    branches:
      - 'release/**'
    paths:
      - 'clang/**'
      - '.github/workflows/libclang-abi-tests.yml'
  pull_request:
    ignore-forks: true
    branches:
      - 'release/**'
    paths:
      - 'clang/**'
      - '.github/workflows/libclang-abi-tests.yml'

concurrency:
  # Skip intermediate builds: always.
  # Cancel intermediate builds: only if it is a pull request build.
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}

jobs:
  abi-dump-setup:
    if: github.repository_owner == 'llvm'
    runs-on: ubuntu-latest
    outputs:
      BASELINE_REF: ${{ steps.vars.outputs.BASELINE_REF }}
      ABI_HEADERS: ${{ steps.vars.outputs.ABI_HEADERS }}
      ABI_LIBS: ${{ steps.vars.outputs.ABI_LIBS }}
      BASELINE_VERSION_MAJOR: ${{ steps.vars.outputs.BASELINE_VERSION_MAJOR }}
      BASELINE_VERSION_MINOR: ${{ steps.vars.outputs.BASELINE_VERSION_MINOR }}
      LLVM_VERSION_MAJOR: ${{ steps.version.outputs.LLVM_VERSION_MAJOR }}
      LLVM_VERSION_MINOR: ${{ steps.version.outputs.LLVM_VERSION_MINOR }}
      LLVM_VERSION_PATCH: ${{ steps.version.outputs.LLVM_VERSION_PATCH }}
    steps:
      - name: Checkout source
        uses: actions/checkout@v3
        with:
          fetch-depth: 250

      - name: Get LLVM version
        id: version
        uses: llvm/actions/get-llvm-version@main

      - name: Setup Variables
        id: vars
        run: |
          minor_version=0
          remote_repo='https://github.com/llvm/llvm-project'
          if [ ${{ steps.version.outputs.LLVM_VERSION_MINOR }} -ne 0 -o ${{ steps.version.outputs.LLVM_VERSION_PATCH }} -eq 0 ]; then
            major_version=$(( ${{ steps.version.outputs.LLVM_VERSION_MAJOR }} - 1))
            baseline_ref="llvmorg-$major_version.0.0"

            # If there is a minor release, we want to use that as the base line.
            minor_ref=$(git ls-remote --refs -t $remote_repo llvmorg-$major_version.[1-9].[0-9] | tail -n1 | grep -o 'llvmorg-.\+' || true)
            if [ -n "$minor_ref" ]; then
               baseline_ref=$minor_ref
            else
              # Check if we have a release candidate
              rc_ref=$(git ls-remote --refs -t $remote_repo llvmorg-$major_version.[1-9].[0-9]-rc* | tail -n1 | grep -o 'llvmorg-.\+' || true)
              if [ -n "$rc_ref" ]; then
                baseline_ref=$rc_ref
              fi
            fi
            echo "BASELINE_VERSION_MAJOR=$major_version" >> $GITHUB_OUTPUT
            echo "BASELINE_REF=$baseline_ref" >> $GITHUB_OUTPUT
            echo "ABI_HEADERS=clang-c" >> $GITHUB_OUTPUT
            echo "ABI_LIBS=libclang.so" >> $GITHUB_OUTPUT
          else
            echo "BASELINE_VERSION_MAJOR=${{ steps.version.outputs.LLVM_VERSION_MAJOR }}" >> $GITHUB_OUTPUT
            echo "BASELINE_REF=llvmorg-${{ steps.version.outputs.LLVM_VERSION_MAJOR }}.0.0" >> $GITHUB_OUTPUT
            echo "ABI_HEADERS=." >> $GITHUB_OUTPUT
            echo "ABI_LIBS=libclang.so libclang-cpp.so" >> $GITHUB_OUTPUT
          fi

  abi-dump:
    if: github.repository_owner == 'llvm'
    needs: abi-dump-setup
    runs-on: ubuntu-latest
    strategy:
      matrix:
        name:
          - build-baseline
          - build-latest
        include:
          - name: build-baseline
            llvm_version_major: ${{ needs.abi-dump-setup.outputs.BASELINE_VERSION_MAJOR }}
            ref: ${{ needs.abi-dump-setup.outputs.BASELINE_REF }}
            repo: llvm/llvm-project
          - name: build-latest
            llvm_version_major: ${{ needs.abi-dump-setup.outputs.LLVM_VERSION_MAJOR }}
            ref: ${{ github.sha }}
            repo: ${{ github.repository }}
    steps:
      - name: Install Ninja
        uses: llvm/actions/install-ninja@main
      - name: Install abi-compliance-checker
        run: |
          sudo apt-get install abi-dumper autoconf pkg-config
      - name: Install universal-ctags
        run: |
          git clone https://github.com/universal-ctags/ctags.git
          cd ctags
          ./autogen.sh
          ./configure
          sudo make install
      - name: Download source code
        uses: llvm/actions/get-llvm-project-src@main
        with:
          ref: ${{ matrix.ref }}
          repo: ${{ matrix.repo }}
      - name: Configure
        run: |
          mkdir install
          cmake -B build -S llvm -G Ninja -DLLVM_ENABLE_PROJECTS=clang -DCMAKE_BUILD_TYPE=Debug -DLLVM_TARGETS_TO_BUILD="" -DLLVM_BUILD_LLVM_DYLIB=ON -DLLVM_LINK_LLVM_DYLIB=ON -DCMAKE_C_FLAGS_DEBUG="-g1 -Og" -DCMAKE_CXX_FLAGS_DEBUG="-g1 -Og" -DCMAKE_INSTALL_PREFIX=$(pwd)/install llvm
      - name: Build
        run: ninja -C build/ ${{ needs.abi-dump-setup.outputs.ABI_LIBS }} install-clang-headers
      - name: Dump ABI
        run: |
          parallel abi-dumper -lver ${{ matrix.ref }} -skip-cxx -public-headers ./install/include/${{ needs.abi-dump-setup.outputs.ABI_HEADERS }} -o {}-${{ matrix.ref }}.abi ./build/lib/{} ::: ${{ needs.abi-dump-setup.outputs.ABI_LIBS }}
          for lib in ${{ needs.abi-dump-setup.outputs.ABI_LIBS }}; do
            # Remove symbol versioning from dumps, so we can compare across major versions.
            sed -i 's/LLVM_[0-9]\+/LLVM_NOVERSION/' $lib-${{ matrix.ref }}.abi
          done
      - name: Upload ABI file
        uses: actions/upload-artifact@v3
        with:
          name: ${{ matrix.name }}
          path: '*${{ matrix.ref }}.abi'

  abi-compare:
    if: github.repository_owner == 'llvm'
    runs-on: ubuntu-latest
    needs:
      - abi-dump-setup
      - abi-dump
    steps:
      - name: Download baseline
        uses: actions/download-artifact@v3
        with:
          name: build-baseline
      - name: Download latest
        uses: actions/download-artifact@v3
        with:
          name: build-latest

      - name: Install abi-compliance-checker
        run: sudo apt-get install abi-compliance-checker
      - name: Compare ABI
        run: |
          for lib in ${{ needs.abi-dump-setup.outputs.ABI_LIBS }}; do
            abi-compliance-checker -lib $lib -old build-baseline/$lib*.abi -new build-latest/$lib*.abi
          done
      - name: Upload ABI Comparison
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: compat-report-${{ github.sha }}
          path: compat_reports/
